package de.negativrekord.blockworld;

import cc.creativecomputing.control.CCControl;
import cc.creativecomputing.graphics.CCCamera;
import cc.creativecomputing.math.CCMath;
import cc.creativecomputing.math.CCVector2f;
import cc.creativecomputing.math.CCVector3f;
import cc.creativecomputing.math.spline.CCCatmulRomSpline;
import cc.creativecomputing.math.spline.CCLinearSpline;
import cc.creativecomputing.math.spline.CCSpline;
import de.negativrekord.blockworld.geometry.Sector;

public class MultiPathGenerator {
	
	
	float maxTime = 100f;
	float timePassed = 0f;
	float scale[] = new float[20];
	
	public enum StartPositions {
		RANDOM,
		GRID
	}
	
	@CCControl(name="speed", min=0, max=1)
	float _cSpeed = 0f;

	CCVector2f[] positions;
	CCVector2f[] lastPositions;
	CCVector2f[] directions;

	CCSpline[] splines = new CCCatmulRomSpline[20];
	Sector sector;
	int nSteps = 0;
	int k = 0;
	
	public MultiPathGenerator (int nPaths, StartPositions startPosition,
							   int size, int offsetX, int offsetY, int offsetZ) {
		
		positions  = new CCVector2f[nPaths];
		lastPositions  = new CCVector2f[nPaths];
		directions = new CCVector2f[nPaths];
		sector = new Sector (new CCVector3f (-size/2, 0, -size/2), new CCVector3f(0,1,0), new CCVector2f(0,0), size);
		
		for (int i=0; i<20; i++) {
			splines[i] = new CCCatmulRomSpline (0.5f, false);
			scale[i] = 1f;
		}
		
		for (int i=0; i<nPaths; i++) {
			switch (startPosition) {
			case GRID:
				for (int j=0; j<nSteps; j++) {
					positions[i] = new CCVector2f(0,0);
					lastPositions[i] = new CCVector2f(0,0);
				}
				break;
			case RANDOM:
				float r0 = CCMath.random(-size/4, size/4);
				float r1 = CCMath.random(-size/4, size/4);
				positions[i] = new CCVector2f (r0+offsetX, r1+offsetY);
				lastPositions[i] = new CCVector2f (r0+offsetX, r1+offsetY);
				break;
			default:
				positions[i] = new CCVector2f(0,0);
				lastPositions[i] = new CCVector2f(0,0);
				break;
			}
			directions[i] = new CCVector2f (CCMath.random(-6,6),CCMath.random(-6,6));
		}
		
		for (int i=0; i<20; i++) {
			splines[0].addPoint(new CCVector3f(CCMath.random(-size/2, size/2), 800, CCMath.random(-size/2, size/2)));
			splines[1].addPoint(new CCVector3f(CCMath.random(-size/2, size/2), 800, CCMath.random(-size/2, size/2)));
			splines[2].addPoint(new CCVector3f(CCMath.random(-size/2, size/2), 800, CCMath.random(-size/2, size/2)));
		}
	}
	
	public enum PathStyle {
		CIRCLE,
		CUBE,
		FREE,
		PLANE, 
		SPHERE
	}
	
	public void addPath (PathStyle theStyle, int r, int index, float theScale) {
		addPath(theStyle, r, index, theScale, new CCVector3f(0,0,0));
	}
	
	boolean checkSpace (int[][][] space, int N, CCVector3f pos) {
		if (pos.x>=N/2 || pos.y>=N/2 || pos.z>=N/2) return true;
		return (space[(int)pos.x+N/2][(int)pos.y+N/2][(int)pos.z+N/2]==1);
	}
	
	CCVector3f[] possibleDirs = {
			new CCVector3f (1,0,0),
			new CCVector3f(-1,0,0),
			new CCVector3f(0,1,0),
			new CCVector3f(0,-1,0),
			new CCVector3f(0,0,1),
			new CCVector3f(0,0,-1)
		};


	
	CCVector3f randomX(int rim, int N) {
		int x = CCMath.sign(CCMath.random())*(int)CCMath.random(N/2-rim,N/2);
		return new CCVector3f(x, (int)CCMath.random(-N/2,N/2), (int)CCMath.random(-N/2,N/2));
	}
	
	CCVector3f randomY(int rim, int N) {
		int y = CCMath.sign(CCMath.random())*(int)CCMath.random(N/2-rim,N/2);
		return new CCVector3f((int)CCMath.random(-N/2,N/2), y, (int)CCMath.random(-N/2,N/2));
	}
	
	CCVector3f randomZ(int rim, int N) {
		int z = CCMath.sign(CCMath.random())*(int)CCMath.random(N/2-rim,N/2);
		return new CCVector3f((int)CCMath.random(-N/2,N/2), (int)CCMath.random(-N/2,N/2),z);
	}
	
	public void addPathVolumetric (int index, float theScale, int[][][] volume, int rim, int N, int w) {
		scale[index] = theScale;
		splines[index].clear();
		
		
		CCVector3f pos = new CCVector3f();
		do {
			pos = randomX(rim, N);
			if (CCMath.random()<0.33f) pos = randomY(rim,N);
			else if (CCMath.random()<0.66f) pos = randomZ(rim,N);
		} while (checkSpace(volume, N, pos));
		
		CCVector3f dir = new CCVector3f(CCMath.floor(CCMath.random(-1,1.99)),CCMath.floor(CCMath.random(-1,1.99)),CCMath.floor(CCMath.random(-1,1.99)));
		
		for (int i=0; i<400; i++) {
			
			if (CCMath.random()<0.4f) dir = new CCVector3f(CCMath.floor(CCMath.random(-1,1.99)),CCMath.floor(CCMath.random(-1,1.99)),CCMath.floor(CCMath.random(-1,1.99)));
			
			CCVector3f nextPos = new CCVector3f(pos).add(dir);
			while (CCMath.abs(nextPos.x)>N/2 || CCMath.abs(nextPos.y)>N/2 || CCMath.abs(nextPos.z)>N/2 || checkSpace(volume, N, nextPos)) {
				dir = new CCVector3f(CCMath.floor(CCMath.random(-1,1.99)),CCMath.floor(CCMath.random(-1,1.99)),CCMath.floor(CCMath.random(-1,1.99)));
				nextPos = new CCVector3f(pos).add(dir);
			}
			pos = new CCVector3f(nextPos);
			nextPos.scale(50); //.add(new CCVector3f(25,25,25));
			splines[index].addPoint(nextPos);
		}
		//splines[index].curveTension(0.1f);
	}
	
	public void addPath (PathStyle theStyle, int r, int index, float theScale, CCVector3f theOffset) {	
		scale[index] = theScale;
		splines[index].clear();
		splines[index] = getPath(theStyle, r, theScale, theOffset);
	}
	
	public static CCCatmulRomSpline getCloseupPath (int nPositions, int[][] heightMap, int mapSize) {
		
		CCCatmulRomSpline spline = new CCCatmulRomSpline(0.5f, false);
		int N = heightMap.length;
		
		for (int i=0; i<nPositions; i++) {
			int x = (int)CCMath.random(-N/2, N/2);
			int y = (int)CCMath.random(-N/2, N/2);
			CCVector3f pos = new CCVector3f(heightMap[x+N/2][y+N/2]+100,x*mapSize/N,y*mapSize/N);
			spline.addPoint(pos);
		}

		spline.curveTension(1f);
		return spline;
	}
	
	public static CCSpline getPath (PathStyle theStyle, int r, float theScale, CCVector3f theOffset) {	
		return getPath (theStyle, r, theScale, theOffset, 40);
	}
	
	public static CCSpline getPath (PathStyle theStyle, int r, float theScale, CCVector3f theOffset, int nSteps) {	

		if (theStyle == PathStyle.CIRCLE) {
			CCLinearSpline mySpline = new CCLinearSpline ( false);
			
			for (int i=0; i<nSteps; i++) {
				float phi = CCMath.TWO_PI * i * theScale/nSteps + CCMath.PI;
				float x = r*CCMath.cos(phi); //*CCMath.cos(phi);
				float y = r*0.1f*CCMath.cos(phi*4); 
				float z = r*CCMath.sin(phi);
				mySpline.addPoint(new CCVector3f(x,y,z).add(theOffset));
			}
			mySpline.endEditSpline();
			return mySpline;
		}
		CCCatmulRomSpline mySpline = new CCCatmulRomSpline (0.5f, false);
		
		if (theStyle==PathStyle.SPHERE) {
			
			float phi   = CCMath.random(0,CCMath.TWO_PI);
			float theta = CCMath.random(0,CCMath.TWO_PI);
			
			for (int i=0; i<nSteps; i++) {
				float rr = r*CCMath.random(1,1.4f);
				phi += 1f;
				theta += 1f;
				float x = rr*CCMath.cos(phi) * CCMath.sin(theta);
				float y = rr*CCMath.cos(theta); 
				float z = rr*CCMath.sin(phi) * CCMath.sin(theta);
				mySpline.addPoint(new CCVector3f(x,y,z).add(theOffset));
			}
		}
		
		if (theStyle==PathStyle.CUBE) {
			float phi = 0;
			float theta = 0;
			for (int i=0; i<nSteps; i++) {
				if (i%1==0) {
					phi = CCMath.random(0,CCMath.TWO_PI);
					theta = CCMath.random(0,CCMath.TWO_PI);
				}
				float x = r*CCMath.cos(phi) * CCMath.sin(theta);
				float y = r*CCMath.cos(theta); 
				float z = r*CCMath.sin(phi) * CCMath.sin(theta);
				mySpline.addPoint(new CCVector3f(x,y,z).add(theOffset));
			}
		}
		
		if (theStyle==PathStyle.FREE) {
			for (int i=0; i<nSteps; i++) {
				float x = CCMath.random(-r,r);
				float y = CCMath.random(-r,r); 
				float z = CCMath.random(-r,r);
				mySpline.addPoint(new CCVector3f(x,y,z).add(theOffset));
			}
		}
		
		if (theStyle==PathStyle.PLANE) {
			for (int i=0; i<nSteps; i++) {
				float x = CCMath.random(-r,r) + theOffset.x;
				float y = theOffset.y; 
				float z = CCMath.random(-r,r) + theOffset.z;
				mySpline.addPoint(new CCVector3f(x,y,z));
			}
		}
		mySpline.curveTension(1f);
		return mySpline;
	}
	
	public static CCVector3f lerp (CCVector3f i0, CCVector3f i1, float b) {
		CCVector3f out = new CCVector3f();
		out.x = CCMath.blend(i0.x, i1.x, b);
		out.y = CCMath.blend(i0.y, i1.y, b);
		out.z = CCMath.blend(i0.z, i1.z, b);
		return out;
	}
	
	public static CCCatmulRomSpline getLinePath (CCVector3f start, CCVector3f stop) {
		
		CCCatmulRomSpline mySpline = new CCCatmulRomSpline (0.5f, false);
		for (int i=0; i<=50; i++) {
			mySpline.addControlPoints (lerp(start, stop, i/50f));
		}
		mySpline.curveTension(1f);
		return mySpline;
	}
	
	public static CCSpline getCirclePath (float r, float angle, CCVector3f center, int nSteps) {
		CCCatmulRomSpline mySpline = new CCCatmulRomSpline (0.5f, false);
		for (int i=0; i<nSteps; i++) {
			float phi = CCMath.TWO_PI * i/nSteps + angle;
			float x = r*CCMath.cos(phi); //*CCMath.cos(phi);
			float y = 0; 
			float z = r*CCMath.sin(phi);
			mySpline.addPoint(new CCVector3f(x,y,z).add(center));
		}
		mySpline.curveTension(1f);
		return mySpline;
	}
	
	
	public CCVector2f pos (int thePath) {
		if (thePath>=positions.length) return null;
		else {
			return positions[thePath];
		}
	}
	
	public CCVector2f dir (int thePath) {
		return new CCVector2f(directions[thePath]);
	}
	
	public CCVector3f spline (int theSpline) {
		float b = timePassed / maxTime * 0.1f * scale[theSpline];
		return splines[theSpline].interpolate (b);
	}
	
	public CCVector3f splineDir (int theSpline) {
		float blend = timePassed/maxTime * 0.1f * scale[theSpline];
		return new CCVector3f (splines[theSpline].interpolate(blend+0.01f)).subtract(splines[theSpline].interpolate(blend));
	}

	public void setCamera (CCCamera camera, int index) {
		
		CCVector3f pos = spline(index);
		camera.position(pos);
		camera.up(new CCVector3f(spline(index)).normalize());
		camera.target(0,0,0);
		
		if (CCMath.abs(pos.x)>CCMath.abs(pos.y) && CCMath.abs(pos.x)>CCMath.abs(pos.z)) {
			camera.target(-CCMath.sign(pos.x)*10000,0,0);	
		}
		else if (CCMath.abs(pos.y)>CCMath.abs(pos.x) && CCMath.abs(pos.y)>CCMath.abs(pos.z)) {
			camera.target(0,-CCMath.sign(pos.y)*10000,0);	
		}
		else {
			camera.target(0,0,-CCMath.sign(pos.z)*10000);	
		}
	}
	public void update (float theDeltaTime) {

		timePassed += theDeltaTime;
		if (timePassed>maxTime) timePassed -= maxTime;
		
		/*
		for (int i=0; i<positions.length; i++) {
			sector.wrapMove(positions[i], directions[i]);
			positions[i] = new CCVector2f(lastPositions[i]).add(directions[i]);
			lastPositions[i] = new CCVector2f(positions[i]);
		}
		*/
	}
}
